Fix OutputCache caching truncated responses for aborted requests - #66954
Closed
Bellambharath wants to merge 1 commit into
Closed
Fix OutputCache caching truncated responses for aborted requests#66954Bellambharath wants to merge 1 commit into
Bellambharath wants to merge 1 commit into
Conversation
When a request is canceled (RequestAborted), OutputCacheMiddleware could store an empty or truncated response in the cache if: 1. No Content-Length header is present (e.g. removed by ResponseCompression) 2. A decorator stream above OutputCacheStream throws before the write reaches OutputCacheStream, so BufferingEnabled stays true Fix: - In FinalizeCacheBodyAsync, skip storing if RequestAborted is canceled - In ExecuteResponseAsync (locking path), return null if the request was aborted so that waiting concurrent requests are not served a truncated response via TryServeCachedResponseAsync Fixes dotnet#66877
Contributor
|
Thanks for your PR, @Bellambharath. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
BrennanConroy
left a comment
Member
There was a problem hiding this comment.
There should be a test or 2 here. Aborting the request in the middle of writing a response and seeing that future requests don't get the truncated response, and making sure the aborted request doesn't re-run the request pipeline.
| if (httpContext.RequestAborted.IsCancellationRequested) | ||
| { | ||
| _logger.ResponseNotCached(); | ||
| return null; |
Member
There was a problem hiding this comment.
We should be setting executed = true; here so the aborted request doesn't try to serve a cached response or re-run the request pipeline.
Member
|
@Bellambharath are you interested in continuing with this contribution? |
Member
|
Thanks for contribution @Bellambharath; we have an active PR solving same issue here: #68683. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #66877
Problem
OutputCacheMiddleware can cache an empty or truncated response when the request that populates the cache is canceled. The broken entry is then served to all subsequent clients until it expires.
This happens because the existing cacheability checks have two blind spots:
The Content-Length check is bypassed when the header is absent (e.g. ResponseCompression removes it).
A decorator stream layered above OutputCacheStream (e.g. GZipStream) can throw OperationCanceledException before the write reaches OutputCacheStream, so BufferingEnabled stays true even though the body is incomplete.
The locking path (SetLocking(true)) has an additional issue: concurrent requests waiting on the in-flight response are served directly from the OutputCacheEntry returned by ExecuteResponseAsync, bypassing the normal cacheability checks entirely.
Fix
Two targeted changes to OutputCacheMiddleware.cs:
This is the right place because it is the single choke-point before every StoreAsync call. RequestAborted being canceled at this point is a strong, observable signal that the response body is incomplete — it is the token passed to WriteAsync in virtually every IActionResult implementation, and callers are expected to call httpContext.Abort() after catching OperationCanceledException mid-write.
Returning null here causes TryServeCachedResponseAsync to short-circuit, so no truncated entry is handed off to waiting concurrent requests.
Testing
The existing test FinalizeCacheBody_Cache_IfContentLengthAbsent and the locking tests continue to pass unchanged. The repro from the issue (OutputCache + ResponseCompression, client cancels after 1 s, subsequent request should get full payload) is the end-to-end verification scenario.